#include "Shader.h"
#include <iostream>
#include <fstream>
#include <sstream>
#include <GL/glew.h>
using namespace std;

Shader::Shader(const char* vertexPath, const char* fragmentPath) 
	: vertexShaderSrc(nullptr), fragmentShaderSrc(nullptr), shaderProgram(0)
{
	// disk --> ifs
   ifstream vertexIfs(vertexPath, ios::in);
   ifstream fragmentIfs(fragmentPath, ios::in);
	vertexIfs.exceptions(ifstream::failbit | ifstream::badbit);
	fragmentIfs.exceptions(ifstream::failbit | ifstream::badbit);

	try
	{
		if (!vertexIfs.is_open() || !fragmentIfs.is_open())
			throw exception("open file error!");

		// ifs --> ss
		stringstream vertexSs;
		stringstream fragmentSs;

		vertexSs << vertexIfs.rdbuf();
		fragmentSs << fragmentIfs.rdbuf();

		// ss ---> string
		vertexStr = vertexSs.str();
		fragmentStr = fragmentSs.str();

		// string -> cstr
		vertexShaderSrc = vertexStr.c_str();
		fragmentShaderSrc = fragmentStr.c_str();

		// DEBUG
		// cout << vertexShaderSrc << endl << endl <<
		// 	fragmentShaderSrc << endl; 

		// shader
		unsigned int vertexShader, fragmentShader;
		vertexShader = glCreateShader(GL_VERTEX_SHADER);
		fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
		glShaderSource(vertexShader, 1, &vertexShaderSrc, NULL);
		glShaderSource(fragmentShader, 1, &fragmentShaderSrc, NULL);
		glCompileShader(vertexShader);
		CheckCompileErrors(vertexShader, "SHADER");
		glCompileShader(fragmentShader);
		CheckCompileErrors(fragmentShader, "SHADER");

		// shader program
		shaderProgram = glCreateProgram();
		glAttachShader(shaderProgram, vertexShader);
		glAttachShader(shaderProgram, fragmentShader);
		glLinkProgram(shaderProgram);
		CheckCompileErrors(shaderProgram, "PROGRAM");

		// delete shader
		glDeleteShader(fragmentShader);
		glDeleteShader(vertexShader);
	}
	catch (const std::exception& ex)
	{
		cout << ex.what() << endl;
	}
}

Shader::~Shader()
{
}

void Shader::Use()
{
	glUseProgram(shaderProgram);
}

unsigned int Shader::GetShaderProgram()
{
	return this->shaderProgram;
}

void Shader::SetUniform(const string& uniformName, float x)
{
	glUniform1f(GetUniformLocation(uniformName), x);
}

void Shader::SetUniform(const string& uniformName, float x, float y)
{
	glUniform2f(GetUniformLocation(uniformName), x, y);
}

void Shader::SetUniform(const string& uniformName, float x, float y, float z)
{
	glUniform3f(GetUniformLocation(uniformName), x, y, z);
}

void Shader::SetUniform(const string& uniformName, float x, float y, float z, float m)
{
	glUniform4f(GetUniformLocation(uniformName), x, y, z, m);
}

void Shader::SetUniform(const string& uniformName, int x)
{
	glUniform1i(GetUniformLocation(uniformName), x);
}

void Shader::SetUniform(const string& uniformName, int x, int y)
{
	glUniform2i(GetUniformLocation(uniformName), x, y);
}

void Shader::SetUniform(const string& uniformName, int x, int y, int z)
{
	glUniform3i(GetUniformLocation(uniformName), x, y, z);
}

void Shader::SetUniform(const string& uniformName, int x, int y, int z, int m)
{
	glUniform4i(GetUniformLocation(uniformName), x, y, z, m);
}

void Shader::CheckCompileErrors(unsigned int id, const string& type)
{
	GLint success;
	char infoLog[512];

	if (type != "PROGRAM")
	{
		glGetShaderiv(id, GL_COMPILE_STATUS, &success);
		if (!success)
		{
			glGetShaderInfoLog(id, 512, nullptr, infoLog);
			cout << "shader compile error : " << infoLog << endl;
		}
	}
	else
	{
		glGetProgramiv(id, GL_LINK_STATUS, &success);
		if (!success)
		{
			glGetProgramInfoLog(id, 512, nullptr, infoLog);
			cout << "program link error : " << infoLog << endl;
		}
	}
}

unsigned int Shader::GetUniformLocation(const string& uniformName)
{
	return glGetUniformLocation(shaderProgram, uniformName.c_str());
}
